文章内容:用 webpack 打包两个模块(通过 import 语法导入模块),分析打包后的代码,也就是 webpack 运行时代码。
# 代码准备
index.js
// const sum = require('./sum')
import sum, { test } from "./sum";
import * as s from "./sum";
console.log(sum(6, 9));
console.log("test", test);
console.log("s", s);
2
3
4
5
6
7
sum.js
const sum = (a, b) => {
return a + b;
};
export default sum;
export const test = "test";
2
3
4
5
6
webpack.config.js
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "build"),
},
mode: "none",
};
2
3
4
5
6
7
8
在控制台执行
npx webpack
输出 build/main.js 文件
/******/ (() => {
// webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = [
,
/* 0 */ /* 1 */
/***/ (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
/* harmony export */ test: () => /* binding */ test,
/* harmony export */
});
const sum = (a, b) => {
return a + b;
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = sum;
const test = "test";
/***/
},
/******/
];
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
});
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for (var key in definition) {
/******/ if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
/******/ Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
/******/
}
/******/
}
/******/
};
/******/
})();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
/******/
})();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
/******/
}
/******/ Object.defineProperty(exports, "__esModule", { value: true });
/******/
};
/******/
})();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _sum__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__(1);
console.log((0, _sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
console.log("s", _sum__WEBPACK_IMPORTED_MODULE_0__);
})();
/******/
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
然后就可以直接断点调试 build/main.js 啦~
# 运行时代码分析
首先与不包含 ESM 的产物代码(这里用的是《分析一个极简的 Webpack 运行时代码 (opens new window)》文章中的产物代码)做对比
标红的部分是 webpack 为处理 ESM 模块所增加的处理逻辑
# 分析变量与函数
__webpack_modules__
: 是一个数组,存放所有加载到的模块。__webpack_module_cache__
: 是一个对象,对模块执行结果进行缓存,这样能够保证每个模块只被执行一次;__webpack_require__
: 是一个函数,作用是加载模块,如果模块第一次被加载,则通过__webpack_modules__[moduleId]
匹配上对应的模块,并进行缓存;如果是已加载的模块,则直接从__webpack_module_cache__[moduleId]
取;__webpack_require__.d
: 定义 getter 方法__webpack_require__.o
:Object.prototype.hasOwnProperty
__webpack_require__.r
: 注入 ESM 标注属性__webpack_exports__
: 是一个对象,存放导出的内容;
# __webpack_require__.r
注入 ESM 标注属性
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
/******/
}
/******/ Object.defineProperty(exports, "__esModule", { value: true });
/******/
};
/******/
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码解析:
- 在函数体内部,首先通过检查环境中是否存在
Symbol
并且Symbol.toStringTag
是否可用,来判断是否可以使用toStringTag
属性。这一部分代码可以说是对环境的兼容性检测。 - 如果环境中存在
Symbol
并且Symbol.toStringTag
可用,那么就会调用Object.defineProperty
方法,在exports
对象上定义Symbol.toStringTag
属性,属性值为'Module'
。这样做可以使得该模块在被打印或转换为字符串时能够显示为'[object Module]'
。 - 不管上述条件是否成立,都会调用
Object.defineProperty
方法,在exports
对象上定义__esModule
属性,属性值为true
。这个属性是为了表示该模块是一个 ES6 模块,并且在其他模块引入时可以进行相应的处理。
Symbol.toStringTag
Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。
Object.prototype.toString.call(exports) // '[object Module]'
# __webpack_require__.d
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
//
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
- 定义
getter
方法:遍历definition
的key
,如果definition
有这个ke
y 而exports
没有,则在exports
的属性key
上挂载definition[key]
这个getter
方法; getter
方法的目的是:在访问某个导出特性的时候才去计算对应的值;- 这里的
definition
是传入的对象:
{
"default": () => (__WEBPACK_DEFAULT_EXPORT__),
"test": () => (/* binding */ test)
}
# sum.js 源码与 webpack 运行时代码做对比分析
sum.js 源码
const sum = (a, b) => {
return a + b;
}
export default sum;
export const test = 'test';
webpack 运行时代码中的 sum 模块
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
/* harmony export */ "test": () => (/* binding */ test)
/* harmony export */ });
const sum = (a, b) => {
return a + b;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);
const test = 'test';
/***/ })
/******/ ])
通过 __webpack_require__.r
标记 __webpack_exports__
为 ESM 模块,然后通过 __webpack_require__.d
对 __webpack_exports__
的 default
、test
导出特性定义 getter
方法。
# 对整个过程进行分析
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log((0,_sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
console.log("s", _sum__WEBPACK_IMPORTED_MODULE_0__)
})();
- 加载入口模块 index.js;
__webpack_require__.r(__webpack_exports__);
- 将 index.js 标记为 ESM
var _sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
加载 sum 模块
- 如果缓存中存在,则在缓存中取
- 将 sum 模块标记为 ESM
- 为 sum 模块的导出特性定义 getter 方法
将结果赋值给
_sum__WEBPACK_IMPORTED_MODULE_0__
console.log((0,_sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
- 调用
default
对应的getter
方法 - 调用
test
对应的getter
方法
# 小结
本文主要对 webpack 打包含有 ESM 模块的运行时代码进行了分析,主要做了如下一些事情:
用 webpack 提供的模块加载函数加载入口模块;
加载入口模块所依赖的模块;
- 如果缓存中存在,则在缓存中取
- 将 import 导入的模块标记为 ESM(
__esModule
) - 为模块导出特性定义 getter 方法
运行模块中的代码。